//
// Created by YANHAI on 2019/11/15.
//

#pragma once

#include <algorithm>
#include <event2/event.h>

#include "http_request.hpp"
#include "http_response.hpp"
#include "base_request.hpp"

class HTTPServer {
    typedef std::function<void(const Request *, Response *)> HttpCallback;

protected:
    HTTPServer();

    ~HTTPServer();

public:
    static HTTPServer &instance()
    {
        static HTTPServer httpServer;
        return httpServer;
    }

    void set_user_agent(const std::string &user_agent);

    void set_cb(const char *path, const HttpCallback &cb, int method);

    bool listen(ev_uint16_t port, const char *address = "127.0.0.1");

    void start();

public:
    struct Handler {
        const std::string path;
        HttpCallback cb;
        int method;

        Handler(const std::string &path, const HttpCallback &cb, int method) :
                path(path), cb(cb), method(method) {}

        bool operator==(const Request &request) const
        {
            return path == request.uri.path() && method & request.method();
        }
    };

    std::vector<Handler> handlers;
    std::string _user_agent;

private:
    event_base *base;
    evhttp *http;
};

struct HandlerEqual : public std::binary_function<HTTPServer::Handler, Request, bool> {
    bool operator()(const HTTPServer::Handler &handler, const Request &request) const
    {
        return handler.path == request.uri.path() && handler.method & request.method();
    }
};

// detail
static void http_callback(struct evhttp_request *raw_request, void *arg);

inline HTTPServer::HTTPServer()
{
    base = event_base_new();
    if (!base) {
        throw HttpServerInitException("event base new failed.");
    }

    http = evhttp_new(base);
    if (!http) {
        throw HttpServerInitException("evhttp new failed.");
    }
}

inline HTTPServer::~HTTPServer()
{
    if (base)
        event_base_free(base);

    if (http)
        evhttp_free(http);
}

inline void HTTPServer::set_user_agent(const std::string &user_agent)
{
    this->_user_agent = user_agent;
}

inline void HTTPServer::set_cb(const char *path, const HttpCallback &cb, int method)
{
    handlers.emplace_back(path, cb, method);
}

inline bool HTTPServer::listen(ev_uint16_t port, const char *address)
{
    auto r = evhttp_bind_socket(http, address, port);
    if (r != 0) {
        logger::error("http bind {}:{} failed", address, port);
        return false;
    }
    logger::info("listening http://{}:{}", address, port);
    return true;
}

inline void HTTPServer::start()
{
    evhttp_set_gencb(http, http_callback, reinterpret_cast<void *>(this));
    event_base_dispatch(base);
}

static void http_callback(struct evhttp_request *raw_request, void *arg)
{
    Request request(raw_request);
    Response response(raw_request);

    // 两种方式都可以
    auto _this = &HTTPServer::instance();
//    auto _this = reinterpret_cast<HTTPServer *>(arg);

    // check user-agent
    if (!_this->_user_agent.empty()) {
        if (request.user_agent().empty()) {
            response.write_unauthorized();
            return;
        } else if (request.user_agent() != _this->_user_agent) {
            response.write_forbidden();
            return;
        }
    }

    // find 和 find_if 均可以
//    auto it = std::find_if(_this->handlers.cbegin(), _this->handlers.cend(),
//                           std::bind2nd(HandlerEqual(), request));
    auto it = std::find(_this->handlers.cbegin(), _this->handlers.cend(), request);
    if (it != _this->handlers.cend()) {
        try {
            request.parse();
            it->cb(&request, &response);
        } catch (RequestException &e) {
            logger::warn("{}", e.what());
            response.write_bad_request(e);
        } catch (std::exception &e) {
            logger::warn("{}", e.what());
            response.write_internal_server_error(e);
        } catch (...) {
            response.write_not_found();
        }
    } else {
        response.write_not_found();
    }
}
